| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108 |
- 'use client';
- import { useCallback, useState } from 'react';
- import { fetchApi } from '@/lib/utils/client';
- import { FollowMember, FollowListResponse } from '@/types/account/follow';
- import FollowListItem from './FollowListItem';
- type Mode = 'followers' | 'following';
- type Props = {
- mode: Mode;
- memberSID: string;
- initialList: FollowMember[];
- initialTotal: number;
- initialPage?: number;
- perPage?: number;
- };
- export default function FollowList({
- mode,
- memberSID,
- initialList,
- initialTotal,
- initialPage = 1,
- perPage = 20
- }: Props) {
- const [list, setList] = useState<FollowMember[]>(initialList);
- const [total, setTotal] = useState<number>(initialTotal);
- const [nextPage, setNextPage] = useState<number>(initialPage + 1);
- const [loading, setLoading] = useState<boolean>(false);
- const [errorMessage, setErrorMessage] = useState<string|null>(null);
- const hasMore = list.length < total;
- const endpointPath = mode === 'followers' ? 'followers' : 'following';
- const loadMore = useCallback(async () => {
- if (loading || !hasMore) {
- return;
- }
- setLoading(true);
- setErrorMessage(null);
- try {
- const url = `/api/member/${encodeURIComponent(memberSID)}/${endpointPath}?page=${nextPage}&perPage=${perPage}`;
- const res = await fetchApi<FollowListResponse>(url, { method: 'GET', silent: true });
- if (!res.success || !res.data) {
- setErrorMessage(res.message ?? '목록을 불러오지 못했습니다.');
- return;
- }
- const incoming = res.data.list;
- setList((prev) => {
- const seen = new Set(prev.map((m) => m.sid));
- const dedup = incoming.filter((m) => !seen.has(m.sid));
- return [...prev, ...dedup];
- });
- setTotal(res.data.total);
- setNextPage((p) => p + 1);
- } catch (err) {
- console.error('Follow list loadMore failed:', err);
- setErrorMessage(err instanceof Error ? err.message : '목록을 불러오지 못했습니다.');
- } finally {
- setLoading(false);
- }
- }, [loading, hasMore, memberSID, endpointPath, nextPage, perPage]);
- if (list.length === 0) {
- const emptyText = mode === 'followers'
- ? '아직 팔로워가 없습니다.'
- : '아직 팔로우하는 회원이 없습니다.';
- return <p className="user-profile__empty">{emptyText}</p>;
- }
- const sectionLabel = mode === 'followers' ? '팔로워 목록' : '팔로우 목록';
- return (
- <section className="user-profile__follow-section" aria-label={sectionLabel}>
- <p className="user-profile__follow-total" aria-live="polite">
- 전체 {total.toLocaleString()}명
- </p>
- <ul className="user-profile__follow-list" role="list">
- {list.map((member) => (
- <FollowListItem key={member.sid} member={member} />
- ))}
- </ul>
- {errorMessage && (
- <p className="user-profile__follow-error" role="alert">{errorMessage}</p>
- )}
- {hasMore && (
- <button
- type="button"
- className="user-profile__follow-more-btn"
- onClick={loadMore}
- disabled={loading}
- aria-label={loading ? '불러오는 중' : '다음 목록 불러오기'}
- >
- {loading ? '불러오는 중...' : '더 보기'}
- </button>
- )}
- </section>
- );
- }
|